<!DOCTYPE html>
<html lang="en">
<script>
    // 自行添加Totp（可配置多个），key为显示的名称，value为secret（只需要修改此处配置，其他地方无需改动）
    const configs = {
        "GitHub": "GitHUB的16位SECRET值",
        "Gitee": "Gitee的16位SECRET值",
    };
</script>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TOTP 二次验证码</title>
    <style>
        /* 全局设置防止水平滚动 */
        html, body {
            margin: 0;
            padding: 0;
            overflow-x: hidden;
            width: 100%;
            box-sizing: border-box;
            -webkit-overflow-scrolling: touch; /* 平滑滚动体验 */
        }

        * {
            box-sizing: inherit;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            background-color: #f5f5f9;
        }

        .container {
            width: 100%;
            max-width: 800px;
            margin: auto;
            padding: 0 12px 30px 12px;
            box-sizing: border-box;
        }

        #totp-container {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
            gap: 16px;
            padding: 0;
            margin: 0;
        }

        .totp-label {
            background-color: white;
            border-radius: 12px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.07);
            text-align: center;
            padding: 16px;
            transition: all 0.3s ease-in-out;
        }

        .totp-label:hover {
            background-color: #b5ccf8;
            transform: translateY(-4px) scale(1.02);
            box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
        }

        .totp-label h3 {
            margin: 0 0 10px 0;
            color: #333;
            font-size: 1rem;
            font-weight: 600;
        }

        .totp-label p {
            margin: 0;
            font-size: 1.5rem;
            color: #07c1bb;
            font-weight: 600;
        }

        .progress-bar {
            background-color: #deecff;
            border-radius: 10px;
            height: 6px;
            margin-top: 10px;
            overflow: hidden;
        }

        .progress {
            background-color: #07c1bb;
            height: 100%;
            width: 0%;
            border-radius: 10px;
            transition: width 0.1s ease;
        }
    </style>
</head>

<body>
<div class="container">
    <h2 class="title">🔐 TOTP 二次验证码</h2>
    <div id="totp-container"></div>
</div>

<script>
    const timeStep = 30;

    function generateTOTP(secret, timeStep) {
        const key = base32Decode(secret);
        const now = Math.floor(Date.now() / 1000 / timeStep);
        const buffer = new ArrayBuffer(8);
        const view = new DataView(buffer);
        view.setUint32(0, 0, false);
        view.setUint32(4, now, false);
        const b = new Uint8Array(buffer);

        return crypto.subtle.importKey(
            "raw",
            key,
            {name: "HMAC", hash: "SHA-1"},
            false,
            ["sign"]
        ).then((cryptoKey) => {
            return crypto.subtle.sign("HMAC", cryptoKey, b);
        }).then((digest) => {
            const digestArray = new Uint8Array(digest);
            const offset = digestArray[digestArray.length - 1] & 0x0F;
            const truncatedHash = digestArray.slice(offset, offset + 4);
            const code = ((truncatedHash[0] & 0x7F) << 24) |
                (truncatedHash[1] << 16) |
                (truncatedHash[2] << 8) |
                truncatedHash[3];
            return code % 1000000;
        });
    }

    function base32Decode(input) {
        const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
        let bits = 0;
        let value = 0;
        let output = [];

        for (let i = 0; i < input.length; i++) {
            const c = input[i].toUpperCase();
            if (c === '=') {
                break;
            }
            const index = alphabet.indexOf(c);
            if (index === -1) {
                throw new Error('Invalid base32 character');
            }
            value = (value << 5) | index;
            bits += 5;
            if (bits >= 8) {
                output.push((value >> (bits - 8)) & 0xFF);
                bits -= 8;
            }
        }

        return new Uint8Array(output);
    }

    const totpElements = {};
    const progressElements = {};

    function initTotpLabels() {
        const totpContainer = document.getElementById('totp-container');
        for (const [name] of Object.entries(configs)) {
            const totpLabel = document.createElement('div');
            totpLabel.classList.add('totp-label');

            const nameHeading = document.createElement('h3');
            nameHeading.textContent = name;

            const totpCode = document.createElement('p');
            totpCode.textContent = '000000';

            const progressBar = document.createElement('div');
            progressBar.classList.add('progress-bar');

            const progress = document.createElement('div');
            progress.classList.add('progress');
            progressBar.appendChild(progress);

            totpLabel.appendChild(nameHeading);
            totpLabel.appendChild(totpCode);
            totpLabel.appendChild(progressBar);
            totpContainer.appendChild(totpLabel);

            totpElements[name] = totpCode;
            progressElements[name] = progress;
        }
    }

    function updateOutput() {
        const currentTime = Math.floor(Date.now() / 1000);
        const elapsedTime = currentTime % timeStep;
        const progressPercentage = (elapsedTime / timeStep) * 100;

        const promises = [];
        for (const [name, secret] of Object.entries(configs)) {
            promises.push(generateTOTP(secret, timeStep).then((totp) => {
                const totpCodeElement = totpElements[name];
                totpCodeElement.textContent = String(totp).padStart(6, '0');

                const progressElement = progressElements[name];
                progressElement.style.width = `${progressPercentage}%`;
            }));
        }

        Promise.all(promises);
    }

    initTotpLabels();
    updateOutput();
    setInterval(updateOutput, 1000);
</script>
</body>

</html>